#include "Rect.h"

// dependency includes
#include "Circ.h"
#include "Line.h"

Rect::Rect():
Shape(),
width(0.f),
height(0.f) {

}

Rect::Rect(Transform* const position, const Vector2D& dims):
Rect(position, 0.f, 0.f, dims.x, dims.y) {
	
}

Rect::Rect(Transform* const position, float w, float h):
Rect(position, 0.f, 0.f, w, h) {

}

Rect::Rect(Transform* const position, const Vector2D& posOffset, const Vector2D& dims):
Rect(position, posOffset.x, posOffset.y, dims.x, dims.y) {

}

Rect::Rect(Transform* const position, const Vector2D& posOffset, float w, float h):
Rect(position, posOffset.x, posOffset.y, w, h) {

}

Rect::Rect(Transform* const position, float xOffset, float yOffset, const Vector2D& dims):
Rect(position, xOffset, yOffset, dims.x, dims.y) {

}

Rect::Rect(Transform* const position, float xOffset, float yOffset, float w, float h):
Shape(position, xOffset, yOffset),
width(w),
height(h) {
	if (width < 0.f) width = -width;
	if (height < 0.f) height = -height;
}


Rect::~Rect() {

}

float Rect::SqDistanceBetween(float x1, float y1, float x2, float y2) const {
	float dx = x2 - x1;
	float dy = y2 - y1;
	return dx * dx + dy * dy;
}

float Rect::SqDistanceBetween(const Vector2D& p1, const Vector2D& p2) const {
	return SqDistanceBetween(p1.x, p1.y, p2.x, p2.y);
}

float Rect::DistanceBetween(float x1, float y1, float x2, float y2) const {
	return sqrtf(SqDistanceBetween(x1, y1, x2, y2));
}

float Rect::DistanceBetween(const Vector2D& p1, const Vector2D& p2) const {
	return DistanceBetween(p1.x, p1.y, p2.x, p2.y);
}

void Rect::GetBottomLine(Line& l) const {
	l.SetParentPosition(GetParent());
	l.SetDirection(Vector2D::Right());
	l.SetDistance(GetWidth());
	return;
}

void Rect::GetLeftLine(Line& l) const {
	l.SetParentPosition(GetParent());
	l.SetDirection(Vector2D::Up());
	l.SetDistance(GetHeight());
	return;
}

void Rect::GetTopLine(Line& l) const {
	l.SetParentPosition(GetParent());
	l.SetOffset(GetWidth(), GetHeight());
	l.SetDirection(Vector2D::Left());
	l.SetDistance(GetWidth());
	return;
}

void Rect::GetRightLine(Line& l) const {
	l.SetParentPosition(GetParent());
	l.SetOffset(GetWidth(), GetHeight());
	l.SetDirection(Vector2D::Down());
	l.SetDistance(GetHeight());
	return;
}

void Rect::GetClosestPointTo(float x, float y, Vector2D& pOut) const {
	// reset the point location
	pOut.x = 0.f;
	pOut.y = 0.f;

	// check the distance on each side
	float dSqBottom = SqDistanceBetween(x, y, x, GetBottom());
	float dSqLeft = SqDistanceBetween(x, y, GetLeft(), y);
	float dSqTop = SqDistanceBetween(x, y, x, GetTop());
	float dSqRight = SqDistanceBetween(x, y, GetRight(), y);

	// sides to simplify treatment
	enum RectSide {
		BOTTOM,
		LEFT,
		TOP,
		RIGHT
	} winner;

	// check which sides is the closest
	if (dSqBottom < dSqLeft) {
		// dSqLeft eleminated
		// dSqBottom stays
		if (dSqBottom < dSqTop) {
			// dSqTop eleminated
			// dSqBottom stays
			if (dSqBottom < dSqRight) {
				// dSqRight eleminated
				// only dSqBottom left
				winner = BOTTOM;
			} else {
				// dSqBottom eleminated
				// only dSqRight left
				winner = RIGHT;
			}
		} else {
			// dSqBottom eleminated
			// go on with dSqTop
			if (dSqTop < dSqRight) {
				// dSqRight eleminated
				// only dSqTop left
				winner = TOP;
			} else {
				// dSqTop eleminated
				// only dSqRight left
				winner = RIGHT;
			}
		}
	} else {
		// dSqBottom eleminated
		// go on with dSqLeft
		if (dSqLeft < dSqTop) {
			// dSqTop eleminated
			// dSqLeft stays
			if (dSqLeft < dSqRight) {
				// dSqRight eleminated
				// only dSqLeft left
				winner = LEFT;
			} else {
				// dSqLeft eleminated
				// only dSqRight left
				winner = RIGHT;
			}
		} else {
			// dSqLeft eleminated
			// go on with dSqTop
			if (dSqTop < dSqRight) {
				// dSqRight eleminated
				// only dSqTop left
				winner = TOP;
			} else {
				// dSqTop eleminated
				// only dSqRight left
				winner = RIGHT;
			}
		}
	}

	switch (winner) {
	case BOTTOM:
		pOut.y = GetBottom();
		if (Contains(x, pOut.y)) {
			// the point is inside the rectangle
			pOut.x = x;
		} else {
			// the point is not inside the rectangle
			if (x > GetRight()) {
				// it is on the right of the rectangle
				pOut.x = GetRight();
			} else {
				// it is on the left of the rectangle
				pOut.x = GetLeft();
			}
		}
		break;
	case LEFT:
		pOut.x = GetLeft();
		if (Contains(pOut.x, y)) {
			// the point is inside the rectangle
			pOut.y = y;
		} else {
			// the point is not inside the rectangle
			if (y > GetTop()) {
				// it is on the top of the rectangle
				pOut.y = GetTop();
			} else {
				// it is on the bottom of the rectangle
				pOut.y = GetBottom();
			}
		}
		break;
	case TOP:
		pOut.y = GetTop();
		if (Contains(x, pOut.y)) {
			// the point is inside the rectangle
			pOut.x = x;
		} else {
			// the point is not inside the rectangle
			if (x > GetRight()) {
				// it is on the right of the rectangle
				pOut.x = GetRight();
			} else {
				// it is on the left of the rectangle
				pOut.x = GetLeft();
			}
		}
		break;
	case RIGHT:
		pOut.x = GetRight();
		if (Contains(pOut.x, y)) {
			// the point is inside the rectangle
			pOut.y = y;
		} else {
			// the point is not inside the rectangle
			if (y > GetTop()) {
				// it is on the top of the rectangle
				pOut.y = GetTop();
			} else {
				// it is on the bottom of the rectangle
				pOut.y = GetBottom();
			}
		}
		break;
	}

	return;
}

void Rect::GetClosestPointTo(const Vector2D& p, Vector2D& pOut) const {
	GetClosestPointTo(p.x, p.y, pOut);
	return;
}

bool Rect::Contains(float x, float y) const {
	if (x >= GetX() && x <= GetX() + width) {
		// is within x range of the rectangle
		if (y >= GetY() && y <= GetY() + height) {
			// is within y range of the rectangle
			return true;
		}
	}

	// otherwise, it is not inside the rectangle
	return false;
}

bool Rect::Contains(const Vector2D& p) const {
	return Contains(p.x, p.y);
}

bool Rect::CollidesWith(const Line* const l, Vector2D& hitOut) const {
	return l->CollidesWith(this, hitOut);
}

bool Rect::CollidesWith(const Rect* const r, Vector2D& hitOut) const {
	if (GetX() <= r->GetX() + r->GetWidth()) {
		// left <= r->right
		if (GetX() + width >= r->GetX()) {
			// right >= r->left
			if (GetY() <= r->GetY() + r->GetHeight()) {
				// (top / bottom) <= (r->bottom / r->top)
				if (GetY() + height >= r->GetY()) {
					// (bottom / top) >= (r->top / r->bottom)
					return true;
				}
			}
		}
	}

	// otherwise, it does not collider
	return false;
}

bool Rect::CollidesWith(const Circ* const c, Vector2D& hitOut) const {
	return c->CollidesWith(this, hitOut);
}

bool Rect::CollidesWith(const Poly* const p, Vector2D& hitOut) const {
	// not implemented
	return false;
}

void Rect::Draw() const {
	// not implemented
	return;
}

void Rect::COut() const {
	std::cout << "Rectangle: (" << GetX() << ", " << GetY() << ") ---> (" << GetWidth() << ", " << GetHeight() << ")" << std::endl;
	return;
}